﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Obra.Api;
using Obra.Common;

namespace Obra.Sync
{
    /// <summary>
    /// SyncService provider that wraps an IMediaService.
    /// </summary>
    public class MediaServiceSyncServiceProvider :
        ISyncService
    {
        private readonly IMediaService mediaService;

        /// <summary>
        /// IMediaService wrapped as a SyncProvider.
        /// </summary>
        public IMediaService MediaService
        {
            get { return mediaService; }
        }

        private readonly string userName;
        private readonly string password;

        public MediaServiceSyncServiceProvider(
            string userName,
            string password,
            IMediaService mediaService)
        {
            this.userName = userName;
            this.password = password;
            this.mediaService = mediaService;
        }

        #region ISyncService Members

        /// <summary>
        /// The name of this SyncProvider.
        /// </summary>
        public string Name
        {
            get;
            set;
        }

        /// <summary>
        /// The description of this SyncProvider.
        /// </summary>
        public string Description
        {
            get;
            set;
        }

        /// <summary>
        /// Connect this SyncProvider - called before any other action.
        /// </summary>
        public void Connect()
        {
            this.MediaService.LogOn(
                this.userName,
                this.password);
        }

        /// <summary>
        /// Disconnect this SyncProvider - last action called.
        /// </summary>
        public void Disconnect()
        {
            this.MediaService.LogOff();
        }

        public SyncOptions SyncOptions
        {
            get { throw new NotImplementedException(); }
        }

        /// <summary>
        /// Create a media item metadata from an IAlbum and IMedia.
        /// </summary>
        /// <param name="album">
        /// Album containing the media.
        /// </param>
        /// <param name="media">
        /// Media to create a metadata from.
        /// </param>
        /// <returns>
        /// Metadata representing the media file.
        /// </returns>
        private MediaItemMetadata CreateMediaItemMetadata(
            IAlbum album,
            IMedia media)
        {
            string name = String.Format("{0}/{1}", album.Name, media.Name);

            MediaItemMetadata mediaItemMetadata = new MediaItemMetadata()
            {
                Checksum = media.Checksum,
                LastModifiedUtc = (ulong) media.LastModified.ToUniversalTime().Ticks,
                Name = name,
            };

            return mediaItemMetadata;
        }

        /// <summary>
        /// Enumerate the changes for this provider.
        /// </summary>
        public List<MediaItemMetadata> EnumerateMediaFiles()
        {
            var files = from album in this.MediaService.Albums
                        from media in album.Media
                        select this.CreateMediaItemMetadata(album, media);

            return files.ToList();
        }

        private void UploadMediaFile(
            ISyncItemTransfer syncItemTransfer)
        {
            IAlbum album = this.MediaService.Albums.SingleOrDefault(
                x => x.Name == syncItemTransfer.MediaItemMetadata.AlbumName);
            
            if (album == null)
            {
                album = this.MediaService.CreateAlbum(syncItemTransfer.MediaItemMetadata.AlbumName);
            }

            this.MediaService.UploadMediaFile(
                album,
                syncItemTransfer.MediaItemMetadata.MediaName,
                syncItemTransfer.Stream);
        }

        /// <summary>
        /// Get the media for the specified media item metadata.
        /// </summary>
        /// <remarks>
        /// <para>
        /// This method insists and enforces on the following.
        ///   * Only one album exists on media provider with the album name.
        ///   * Only one media exists in the album with the media name.
        /// </para>
        /// </remarks>
        private IMedia GetMedia(
            MediaItemMetadata mediaItemMetadata)
        {
            return (from album in this.MediaService.Albums
                    from media in album.Media
                    where album.Name == mediaItemMetadata.AlbumName && media.Name == mediaItemMetadata.MediaName
                    select media).Single();
        }

        /// <summary>
        /// Checksum a media file.
        /// </summary>
        /// <param name="mediaItemMetadata">
        /// Metadata of the media file to compute the checksum of.
        /// </param>
        /// <returns>
        /// String representation of the media file's checksum.
        /// </returns>
        public string ComputeChecksum(
            MediaItemMetadata mediaItemMetadata)
        {
            return this.GetMedia(mediaItemMetadata).Checksum;
        }

        /// <summary>
        /// Delete a media file from this provider.
        /// </summary>
        /// <param name="mediaItemMetadata">
        /// Metadata for the media file to delete.
        /// </param>
        /// <remarks>
        /// <para>
        /// XXX: This class is explicitly cleaning up for the media service
        /// provider in the case where there are no media left in this album.
        /// I am not sure if this is correct, or not.
        /// </para>
        /// </remarks>
        public void DeleteMediaFile(
            MediaItemMetadata mediaItemMetadata)
        {
            IAlbum album = this.MediaService.Albums.Single(x => x.Name == mediaItemMetadata.AlbumName);
            IMedia media = album.Media.Single(x => x.Name == mediaItemMetadata.MediaName);

            this.MediaService.DeleteMediaFile(
                album,
                media);

            if (album.Media.Count == 0)
            {
                this.MediaService.DeleteAlbum(album);
            }
        }

        /// <summary>
        /// Save a SyncItem to this SyncProvider.
        /// </summary>
        /// <param name="syncItemTransfer">
        /// SyncItem to save.
        /// </param>
        public void SaveChange(
            ISyncItemTransfer syncItemTransfer)
        {
            this.UploadMediaFile(syncItemTransfer);
        }

        /// <summary>
        /// Load a SyncItem from this SyncProvider.
        /// </summary>
        /// <param name="syncItem">
        /// SyncItem to load.
        /// </param>
        /// <returns>
        /// Class wrapping the transfer object.
        /// </returns>
        public ISyncItemTransfer LoadChange(string mediaName)
        {
            MediaItemMetadata mediaItemMetadata = new MediaItemMetadata()
            {
                Name = mediaName,
            };

            ISyncItemTransfer syncItemTransfer = new SyncItemTransferLazyStream(
                mediaItemMetadata,
                () =>
                {
                    return this.MediaService.DownloadMediaFile(
                        this.GetMedia(mediaItemMetadata));
                });

            return syncItemTransfer;
        }

        #endregion
    }
}
